package com.aaron.messenger.data;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.ArrayList;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.util.Xml;

import com.aaron.messenger.data.DataManager.MessageDispatcher;
import com.aaron.messenger.data.User.UserState;
import com.aaron.messenger.net.Broadcast;
import com.aaron.messenger.net.NetPort;
import com.aaron.messenger.net.UdpSender;
import com.aaron.messenger.ui.MainActivity;
import com.aaron.messenger.util.Utils;

/**
 * <message>
 * <cmd>quit</cmd>
 * <data>
 * <ip>*.*.*.*</ip>
 * </data>
 * </message>
 * 
 * <message>
 * <cmd>login</cmd>
 * <state>*</state>
 * <data>
 * <username>***</username>
 * <ip>"*.*.*.*"</ip>
 * <deviceinfo>***</deviceinfo>
 * </data>
 * </message>
 * 
 * <message>
 * <cmd>feedback</cmd>
 * <state>*</state>
 * <data>
 * <username>***</username>
 * <ip>"*.*.*.*"</ip>
 * <deviceinfo>***</deviceinfo>
 * </data>
 * </message>
 * 
 * <message>
 * <cmd>statechanged</cmd>
 * <state>*</state>
 * <data>
 * <ip>*.*.*.*</ip>
 * </data>
 * </message>
 * 
 * */

public class UserManager {
	private static final String TAG = "UserManager";
	
	private ArrayList<User> mUsers = new ArrayList<User>();
	private XmlSerializer mSerializer = Xml.newSerializer();
	
	private static UserManager sInstance;
	private Context mContext;
	private UsersStateReceiver mReceiver;
	private Handler mCallbackHandler;
	private boolean mIsLogin = false;
	
	private UserManager(Context context, Handler handler) {
		mContext = context.getApplicationContext();
		mCallbackHandler = handler;
		startUserStateReceiver();
	}
	
	public synchronized static UserManager getInstance(Context context, Handler handler) {
		if(sInstance == null) {
			sInstance = new UserManager(context, handler);
		}
		return sInstance;
	}
		
	public void setUsersChangedCallback(Handler handler) {
		mCallbackHandler = handler;
	}
	
	public ArrayList<User> getUsers() {
		return mUsers;
	}
	
	public void clearUsers() {
		mUsers.clear();
		if(mCallbackHandler != null) {
			Message msg = mCallbackHandler.obtainMessage(MessageDispatcher.MSG_USER_QUIT, null);
			msg.sendToTarget();
		}
	}
	
	public void exit() {
		mCallbackHandler = null;
		mUsers.clear();
		try {
			quit();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalStateException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		stopUserStateReceiver();
		sInstance = null;
	}
	
	 /**
	 * @throws IOException 
	 * @throws IllegalStateException 
	 * @throws IllegalArgumentException 
	 */
	public void login() throws IllegalArgumentException, IllegalStateException, IOException {
		String message = constructLoginMessage();
		Log.i(TAG,"login:"+message);
		Broadcast.sendBroadcast(message.getBytes("utf-8"));
		mIsLogin = true;
	}
		
	private String constructLoginMessage() throws IllegalArgumentException, IllegalStateException, IOException {
		StringWriter writer = new StringWriter();
		mSerializer.setOutput(writer);
		
		mSerializer.startDocument("utf-8", true);
		mSerializer.startTag(null, "message");
		mSerializer.startTag(null, "cmd");
		mSerializer.text("login");
		mSerializer.endTag(null, "cmd");
		mSerializer.startTag(null, "state");
		mSerializer.text(String.valueOf(UserState.ACTIVE));
		mSerializer.endTag(null, "state");
		mSerializer.startTag(null, "data");
		mSerializer.startTag(null, "username");
		mSerializer.text("hahaha");
		mSerializer.endTag(null, "username");
		mSerializer.startTag(null, "ip");
		mSerializer.text(Utils.peekLocalIp());
		mSerializer.endTag(null, "ip");
		mSerializer.startTag(null, "deviceinfo");
		mSerializer.text(Utils.getDeviceInfo(mContext));
		mSerializer.endTag(null, "deviceinfo");
		mSerializer.endTag(null, "data");
		mSerializer.endTag(null, "message");
		mSerializer.endDocument();
		
		return writer.toString();
	}
	
	private String constructFeedbackMessage() throws IllegalArgumentException, IllegalStateException, IOException {
		StringWriter writer = new StringWriter();
		mSerializer.setOutput(writer);
		
		mSerializer.startDocument("utf-8", true);
		mSerializer.startTag(null, "message");
		mSerializer.startTag(null, "cmd");
		mSerializer.text("feedback");
		mSerializer.endTag(null, "cmd");
		mSerializer.startTag(null, "state");
		mSerializer.text(String.valueOf(UserState.ACTIVE));
		mSerializer.endTag(null, "state");
		mSerializer.startTag(null, "data");
		mSerializer.startTag(null, "username");
		mSerializer.text("hahaha");
		mSerializer.endTag(null, "username");
		mSerializer.startTag(null, "ip");
		mSerializer.text(Utils.peekLocalIp());
		mSerializer.endTag(null, "ip");
		mSerializer.startTag(null, "deviceinfo");
		mSerializer.text(Utils.getDeviceInfo(mContext));
		mSerializer.endTag(null, "deviceinfo");
		mSerializer.endTag(null, "data");
		mSerializer.endTag(null, "message");
		mSerializer.endDocument();
		
		return writer.toString();
	}
	
	/**
	 * @throws IOException 
	 * @throws IllegalStateException 
	 * @throws IllegalArgumentException 
	 */
	public void quit() throws IllegalArgumentException, IllegalStateException, IOException {
		String message = constructQuitMessage();
		Broadcast.sendBroadcast(message.getBytes("utf-8"));
		mIsLogin = false;
	}
	
	public boolean isLogin() {
		return mIsLogin;
	}
	
	private String constructQuitMessage() throws IllegalArgumentException, IllegalStateException, IOException {
		StringWriter writer = new StringWriter();
		mSerializer.setOutput(writer);
		
		mSerializer.startDocument("utf-8", true);
		mSerializer.startTag(null, "message");
		mSerializer.startTag(null, "cmd");
		mSerializer.text("quit");
		mSerializer.endTag(null, "cmd");
		mSerializer.startTag(null, "data");
		mSerializer.startTag(null, "ip");
		mSerializer.text(Utils.peekLocalIp());
		mSerializer.endTag(null, "ip");
		mSerializer.endTag(null, "data");
		mSerializer.endTag(null, "message");
		mSerializer.endDocument();
		
		return writer.toString();
	}
	
	private void feedbackWeToPeer(String target) throws IllegalArgumentException, IllegalStateException, IOException {
		String message = constructFeedbackMessage();
		Log.i(TAG,"feedBack:"+message+"\n to:"+target);
		UdpSender.sendUdp(message.getBytes("utf-8"), target, NetPort.USERMANAGER_PORT);
	}
	
	public void OnUserLogin(User user) {
		// TODO Auto-generated method stub
		User u = findUser(user);
		if(u == null) {
			mUsers.add(user);
			if(mCallbackHandler != null) {
				Message msg = mCallbackHandler.obtainMessage(MessageDispatcher.MSG_USER_LOGIN, user);
				msg.sendToTarget();
			}
		} 
		try {
			feedbackWeToPeer(user.ip);
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalStateException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void OnUserFeedBack(User user) {
		User u = findUser(user);
		if(u == null) {
			mUsers.add(user);
			if(mCallbackHandler != null) {
				Message msg = mCallbackHandler.obtainMessage(MessageDispatcher.MSG_USER_FEEDBACK);
				msg.sendToTarget();
			}
		}
	}
	
	public void OnUserQuit(User user) {
		User u = findUser(user);
		if(u != null) {
			mUsers.remove(u);
			if(mCallbackHandler != null) {
				Message msg = mCallbackHandler.obtainMessage(MessageDispatcher.MSG_USER_QUIT, u);
				msg.sendToTarget();
			}
		}
		
	}

	public void OnUserStateChanged(User user) {
		User u = findUser(user);
		if(u != null && u.getState() != user.getState()) {
			u.setState(user.getState());
			if(mCallbackHandler != null) {
				Message msg = mCallbackHandler.obtainMessage(MessageDispatcher.MSG_USER_STATE_CHANGED, u);
				msg.sendToTarget();
			}
		}
	}
	
	private User findUser(User user) {
		for(User item:mUsers) {
			if(item.equals(user)) {
				return item;
			}
		}
		return null;
	}
	
	private void startUserStateReceiver() {
		if(mReceiver == null) {
			mReceiver = new UsersStateReceiver("BroadcastReceiver", this);
			mReceiver.start();
		}
	}
	
	private void stopUserStateReceiver() {
		if(mReceiver != null) {
			mReceiver.mReceiverSocket.close();
			mReceiver = null;
		}
	}
	
	static class UsersStateReceiver extends Thread {
				
		DatagramSocket mReceiverSocket;
		XmlPullParser mParser;
		UserManager mM;
		
		public UsersStateReceiver(String name, UserManager manager) {
			super(name);
			mM = manager;
		}
				
		public void run() {
			try {
				mReceiverSocket = new DatagramSocket(NetPort.USERMANAGER_PORT);
				XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
				mParser = factory.newPullParser();
			} catch (SocketException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (XmlPullParserException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 
			while(true) {
				Log.i(TAG,"startReceiver");
				byte[] data = new byte[1024]; 
				DatagramPacket pack = new DatagramPacket(data, 1024);
				try {
					mReceiverSocket.receive(pack);
					parserPacket(data,pack.getLength());
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					if(!mReceiverSocket.isClosed()) {
						mReceiverSocket.close();
					}
					mReceiverSocket = null;
					return;
				}
			}
			
		}
		
		private void parserPacket(byte[] data, int datalength) {
			String info = new String(data,0,datalength);
			Log.i(TAG,"Receive:"+info);
			User user = null;
			String cmd = null;
			String tagName = null;
			try {
				mParser.setInput(new StringReader(info));
				int eventType = mParser.getEventType();
				while(eventType != XmlPullParser.END_DOCUMENT) {
					switch(eventType) {
					case XmlPullParser.START_DOCUMENT:
						user = new User();
						break;
					case XmlPullParser.START_TAG:
						tagName = mParser.getName();
						break;
					case XmlPullParser.END_TAG:
						break;
					case XmlPullParser.TEXT:
						String text = mParser.getText();
						if(tagName.equals("cmd")) {
							cmd = text;
						}
						if(tagName.equals("ip")) {
							if(text.equals(Utils.peekLocalIp())) {
								return;
							}
							user.setIp(text);
						}
						if(tagName.equals("username")) {
							user.setName(text);
						}
						if(tagName.equals("deviceinfo")) {
							user.setDeviceInfo(text);
						}
						if(tagName.equals("state")) {
							user.setState(UserState.valueOf(text));
						}
						break;
					}
					eventType = mParser.next();
				}
				if(cmd == null) {
					return;
				}
				if(cmd.equals("login")) {
					mM.OnUserLogin(user);
				} else if(cmd.equals("feedback")) {
					mM.OnUserFeedBack(user);
				} else if(cmd.equals("quit")) {
					mM.OnUserQuit(user);
				} else if(cmd.equals("statechanged")) {
					mM.OnUserStateChanged(user);
				}
			} catch (XmlPullParserException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	
}
